#include "alsatool.h"

#include <QDebug>

WriteAudioThread::WriteAudioThread(QObject *parent) : QThread(parent)
{
    alsaPlaybackVolume("Headphone", 121);
}

WriteAudioThread::~WriteAudioThread()
{
    freeHandle(handle, handle1, handle2, handle3, handle4, handle5);
}

int WriteAudioThread::set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, unsigned int channels, unsigned int rate,
                                   snd_pcm_uframes_t &buffer_size, snd_pcm_uframes_t &period_size)
{
    unsigned int      rrate;
    snd_pcm_uframes_t size;
    int               err, dir;
    /* choose all parameters */
    err = snd_pcm_hw_params_any(handle, params);
    if (err < 0)
    {
        qDebug() << "Broken configuration for playback: no configurations available: " << snd_strerror(err);
        return err;
    }
    /* set hardware resampling */
    err = snd_pcm_hw_params_set_rate_resample(handle, params, 1);
    if (err < 0)
    {
        qDebug() << "Resampling setup failed for playback: " << snd_strerror(err);
        return err;
    }

    /* set the interleaved read/write format */
    err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err < 0)
    {
        qDebug() << "Access type not available for playback: " << snd_strerror(err);
        return err;
    }
    /* set the sample format */
    err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
    if (err < 0)
    {
        qDebug() << "Sample format not available for playback: " << snd_strerror(err);
        return err;
    }
    /* set the count of channels */
    err = snd_pcm_hw_params_set_channels(handle, params, channels);
    if (err < 0)
    {
        qDebug() << QString("Channels count (%1) not available for playbacks: %2").arg(channels).arg(snd_strerror(err));
        return err;
    }
    /* set the stream rate */
    rrate = rate;
    err   = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
    if (err < 0)
    {
        qDebug() << QString("Rate %1Hz not available for playback: %2").arg(rate).arg(snd_strerror(err));
        return err;
    }
    if (rrate != rate)
    {
        qDebug() << QString("Rate doesn't match (requested %1Hz, get %2Hz)\n").arg(rate).arg(err);
        return -EINVAL;
    }
    /* set the buffer time */
    unsigned int buffer_time = 128000; /* ring buffer length in us */
    err                      = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
    if (err < 0)
    {
        qDebug() << QString("Unable to set buffer time %1 for playback: %2").arg(buffer_time).arg(snd_strerror(err));
        return err;
    }
    err = snd_pcm_hw_params_get_buffer_size(params, &size);
    if (err < 0)
    {
        qDebug() << QString("Unable to get buffer size for playback: %1").arg(snd_strerror(err));
        return err;
    }
    buffer_size = size;
    qDebug() << "buffer_size = " << int(buffer_size);
    /* set the period time */
    unsigned int period_time = 12800; /* period time in us */

    err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
    if (err < 0)
    {
        qDebug() << QString("Unable to set period time %1 for playback: %2").arg(period_time).arg(snd_strerror(err));
        return err;
    }
    err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
    if (err < 0)
    {
        qDebug() << "Unable to get period size for playback: " << snd_strerror(err);
        return err;
    }
    period_size = size;
    // qDebug() << "period_size = " << int(period_size);
    /* write the parameters to device */
    err = snd_pcm_hw_params(handle, params);
    if (err < 0)
    {
        qDebug() << "Unable to set hw params for playback: " << snd_strerror(err);
        return err;
    }

    return 0;
}

int WriteAudioThread::set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, snd_pcm_uframes_t &buffer_size, snd_pcm_uframes_t &period_size)
{
    int err;
    /* get the current swparams */
    err = snd_pcm_sw_params_current(handle, swparams);
    if (err < 0)
    {
        qDebug() << "Unable to determine current swparams for playback: " << snd_strerror(err);
        return err;
    }
    /* start the transfer when the buffer is almost full: */
    /* (buffer_size / avail_min) * avail_min */
    err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);
    //    err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size / 4);
    //    err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0);
    if (err < 0)
    {
        qDebug() << "Unable to set start threshold mode for playback: " << snd_strerror(err);
        return err;
    }
    /* allow the transfer when at least period_size samples can be processed */
    /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
    err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
    if (err < 0)
    {
        qDebug() << "Unable to set avail min for playback: " << snd_strerror(err);
        return err;
    }

    /* write the parameters to the playback device */
    err = snd_pcm_sw_params(handle, swparams);
    if (err < 0)
    {
        qDebug() << "Unable to set sw params for playback: " << snd_strerror(err);
        return err;
    }

    return 0;
}

/*
 *   Underrun and suspend recovery
 */
int WriteAudioThread::xrun_recovery(snd_pcm_t *handle, int err)
{
    qDebug() << "stream recovery";
    if (err == -EPIPE)
    { /* under-run */
        err = snd_pcm_prepare(handle);
        if (err < 0) qDebug() << "Can't recovery from underrun, prepare failed: " << snd_strerror(err);
        return 0;
    }
    else if (err == -ESTRPIPE)
    {
        while ((err = snd_pcm_resume(handle)) == -EAGAIN) sleep(1); /* wait until the suspend flag is released */
        if (err < 0)
        {
            err = snd_pcm_prepare(handle);
            if (err < 0) qDebug() << "Can't recovery from suspend, prepare failed: " << snd_strerror(err);
        }
        return 0;
    }
    return err;
}

void WriteAudioThread::dealVolDone(int vol)
{
    alsaPlaybackVolume("Playback", vol);
}

void WriteAudioThread::alsaPlaybackVolume(QString element, int value)
{
    long leftVal = 0, rightVal = 0;
    if (value > 255)
    {
        value = 255;
        qDebug() << "播放音量值超出范围0~255";
    }
    else if (value < 0)
    {
        value = 0;
        qDebug() << "播放音量值超出范围0~255";
    }

    snd_mixer_t *     mixer;
    snd_mixer_elem_t *pcm_element;

    int err = 0;

    if ((err = snd_mixer_open(&mixer, 0) < 0))
    {
        qDebug() << " snd_mixer_open Err \n";
        return;
    }

    snd_mixer_attach(mixer, "default");
    snd_mixer_selem_register(mixer, NULL, NULL);
    snd_mixer_load(mixer);
    pcm_element = snd_mixer_first_elem(mixer);

    while (pcm_element)
    {
        if (QString(snd_mixer_selem_get_name(pcm_element)) == element)
        {
            snd_mixer_selem_set_playback_volume_all(pcm_element, value);
            snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_LEFT, &leftVal);
            snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_RIGHT, &rightVal);
            qDebug("%s NOW vol :leftVal = %ld, rightVal  = %ld", snd_mixer_selem_get_name(pcm_element), leftVal, rightVal);
            break;
        }
        else
        {
            pcm_element = snd_mixer_elem_next(pcm_element);
        }
    }
    snd_mixer_free(mixer);
}

void WriteAudioThread::freeHandle(snd_pcm_t *handle0, snd_pcm_t *handle1, snd_pcm_t *handle2, snd_pcm_t *handle3, snd_pcm_t *handle4,
                                  snd_pcm_t *handle5)
{
    if (handle0 != NULL)
    {
        snd_pcm_drain(handle0);
        snd_pcm_close(handle0);
        snd_pcm_hw_free(handle0);
    }

    if (handle1 != NULL)
    {
        snd_pcm_drain(handle1);
        snd_pcm_close(handle1);
        snd_pcm_hw_free(handle1);
    }

    if (handle2 != NULL)
    {
        snd_pcm_drain(handle2);
        snd_pcm_close(handle2);
        snd_pcm_hw_free(handle2);
    }

    if (handle3 != NULL)
    {
        snd_pcm_drain(handle3);
        snd_pcm_close(handle3);
        snd_pcm_hw_free(handle3);
    }

    if (handle4 != NULL)
    {
        snd_pcm_drain(handle4);
        snd_pcm_close(handle4);
        snd_pcm_hw_free(handle4);
    }

    if (handle5 != NULL)
    {
        snd_pcm_drain(handle5);
        snd_pcm_close(handle5);
        snd_pcm_hw_free(handle5);
    }
}

void WriteAudioThread::run()
{
    const unsigned int   channels   = 1;
    const unsigned int   rate       = AUDIO_RATE;
    const int            packetsize = PACKETSIZE;
    snd_pcm_uframes_t    buffer_size;
    snd_pcm_uframes_t    period_size;
    int                  err = 0;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    snd_pcm_hw_params_alloca(&hwparams);
    snd_pcm_sw_params_alloca(&swparams);

    if ((err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = snd_pcm_open(&handle1, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = snd_pcm_open(&handle2, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = snd_pcm_open(&handle3, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = snd_pcm_open(&handle4, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = snd_pcm_open(&handle5, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        qDebug() << "Playback open error: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = set_hwparams(handle, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_hwparams(handle1, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_hwparams(handle2, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_hwparams(handle3, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_hwparams(handle4, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_hwparams(handle5, hwparams, channels, rate, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of hwparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    qDebug() << "Write buffer_size = " << buffer_size;
    qDebug() << "Write period_size = " << period_size;
    if ((err = set_swparams(handle, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_swparams(handle1, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_swparams(handle2, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_swparams(handle3, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }

    if ((err = set_swparams(handle4, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    if ((err = set_swparams(handle5, swparams, buffer_size, period_size)) < 0)
    {
        qDebug() << "Setting of swparams failed: " << snd_strerror(err);
        exit(EXIT_FAILURE);
    }
    qDebug() << "Write period_size = " << period_size;

    QByteArray audiodata;
    QByteArray audiodata1;
    QByteArray audiodata2;
    QByteArray audiodata3;
    QByteArray audiodata4;
    QByteArray audiodata5;
    qDebug() << "音频接收线程ID: " << QThread::currentThreadId();

    QList<QHostAddress> host_ip;
    int                 host_cnt[6];
    int                 offset = 300;

    while (1)
    {
        if (!isStart)
        {
            msleep(20);
            continue;
        }

        for (int i = 0; i < 6; i++)
        {
            if (host_cnt[i] > 0)
            {
                host_cnt[i]--;
            }
            else
            {
                host_ip.removeAt(i);
            }
        }

        if (m_pRecvManStreamSocket.hasPendingDatagrams())
        {
            char         data[packetsize + offset] = {0};
            QHostAddress host;
            quint16      port;
            quint16      size;
            size = m_pRecvManStreamSocket.pendingDatagramSize();
            m_pRecvManStreamSocket.readDatagram(data, size, &host, &port);
            if (data[filter] != 1) continue;

            if (!host_ip.contains(host))
            {
                host_ip.append(host);
            }
            switch (host_ip.indexOf(host))
            {
                case 0:
                    host_cnt[0] = AUDIO_POST_CNT;
                    audiodata.append(&data[offset], size - offset);
                    break;
                case 1:
                    host_cnt[1] = AUDIO_POST_CNT;
                    audiodata1.append(&data[offset], size - offset);
                    break;
                case 2:
                    host_cnt[2] = AUDIO_POST_CNT;
                    audiodata2.append(&data[offset], size - offset);
                    break;
                case 3:
                    host_cnt[3] = AUDIO_POST_CNT;
                    audiodata3.append(&data[offset], size - offset);
                    break;
                case 4:
                    host_cnt[4] = AUDIO_POST_CNT;
                    audiodata4.append(&data[offset], size - offset);
                    break;
                case 5:
                    host_cnt[5] = AUDIO_POST_CNT;
                    audiodata5.append(&data[offset], size - offset);
                    break;
                default:
                    break;
            }
        }
        else
        {
            msleep(10);
        }

        if (audiodata.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata.data(), packetsize);
            audiodata.remove(0, packetsize);
            err = snd_pcm_writei(handle, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle, err) < 0)
                {
                    qDebug() << "handle Write error: " << snd_strerror(err);
                }
            }
        }
        if (audiodata1.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata1.data(), packetsize);
            audiodata1.remove(0, packetsize);
            err = snd_pcm_writei(handle1, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle1, err) < 0)
                {
                    qDebug() << " handle1 Write error: " << snd_strerror(err);
                }
            }
        }

        if (audiodata2.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata2.data(), packetsize);
            audiodata2.remove(0, packetsize);
            err = snd_pcm_writei(handle2, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle2, err) < 0)
                {
                    qDebug() << " handle2 Write error: " << snd_strerror(err);
                }
            }
        }

        if (audiodata3.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata3.data(), packetsize);
            audiodata3.remove(0, packetsize);
            err = snd_pcm_writei(handle3, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle3, err) < 0)
                {
                    qDebug() << " handle3 Write error: " << snd_strerror(err);
                }
            }
        }

        if (audiodata4.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata4.data(), packetsize);
            audiodata4.remove(0, packetsize);
            err = snd_pcm_writei(handle4, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle4, err) < 0)
                {
                    qDebug() << " handle4 Write error: " << snd_strerror(err);
                }
            }
        }

        if (audiodata5.size() > 2048)
        {
            char data[packetsize] = {0};
            memcpy(data, audiodata5.data(), packetsize);
            audiodata5.remove(0, packetsize);
            err = snd_pcm_writei(handle5, data, packetsize / (2 * channels));
            if (err < 0)
            {
                if (xrun_recovery(handle5, err) < 0)
                {
                    qDebug() << " handle5 Write error: " << snd_strerror(err);
                }
            }
        }
    }
}

void WriteAudioThread::setMulticastGroup(const QHostAddress &groupAddress, quint16 port)
{
    m_pRecvManStreamSocket.bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
    m_pRecvManStreamSocket.joinMulticastGroup(groupAddress);
}

void WriteAudioThread::destroyMulticastGroup(const QHostAddress &groupAddress)
{
    if (m_pRecvManStreamSocket.isOpen())
    {
        m_pRecvManStreamSocket.leaveMulticastGroup(groupAddress);
        m_pRecvManStreamSocket.close();
    }
}

void WriteAudioThread::setProcessFlag(bool flag)
{
    isStart = flag;
}

void WriteAudioThread::filterAudio(uint8_t ip)
{
    filter = ip;
}
